#pragma once
#include "Sock.hpp"
#include <sys/select.h>
const static uint16_t defaultport = 8080;
// typedef int type_t;
#define RECV_EVENT   (0X1)
#define WRITE_EVENT  (0X1 << 1)
#define EXCEPT_EVENT (0X1 << 2)
#define defaultevent 0
typedef struct FdEvent
{
   int fd;
   uint8_t event;
   std::string clientip;
   uint16_t clientport;
}type_t;

class SelectServer
{
public:
    static const int N = sizeof(fd_set) * 8; // 服务器最多能容纳N个连接
    SelectServer(uint16_t port = defaultport) : port_(port)
    {
    }
    void InitServer()
    {
        // 1.创建socket套接字
        listensock_.Socket();
        // 2.Bind端口号，云服务器不需要绑定ip
        listensock_.Bind(port_);
        // 3.listen监听
        listensock_.Listen();
        for (int i = 0; i < N; i++)
        {
            fdarray[i].fd = defaultscok;
            fdarray[i].event = defaultevent;
            fdarray[i].clientport = 0;
        }
    }
    void Accepter()
    {
        std::cout << "有一个新的连接到来了" << std::endl;
        // 在这里进行Accept会不会阻塞？不会
        std::string clientip;
        uint16_t clientport;
        int sock = listensock_.Accept(&clientip, &clientport);
        if(sock < 0) return;
        // accept成功之后，能够直接进行read/recv吗？
        // 不能，因为你怎么知道sock上已经有数据，所以我们需要将sock交给select，让select帮我们管理。

        // 怎么保证select每次轮询的时候，监听内容已经更新了？
        // select服务器，使用的时候，需要程序员自己维护一个第三方数组，来进行已经获得的sock进行管理！
        logMessage(Debug, "[%s,%d],sock: %d", clientip.c_str(), clientport, sock);
        int pos;
        for (pos = 1; pos < N; pos++)
        {
            if (fdarray[pos].fd == defaultscok)
                break;
        }
        if (pos >= N)
        {
            // 服务器能管理的接口已经满了
            close(sock);
            logMessage(Warning, "服务器能管理的接口已经满了，新的客户端不可再建立连接了");
            return;
        }else 
        {
            fdarray[pos].fd = sock;
            //读事件
            fdarray[pos].event = RECV_EVENT;
            fdarray[pos].clientip = clientip;
            fdarray[pos].clientport = clientport;
        }
    }
    void Recv(int index)
    {
         // listensock套接字
        char buffer[1024];
        ssize_t s = recv(fdarray[index].fd,buffer,sizeof(buffer) - 1,0);  //不会被阻塞
        if(s > 0)
        {
            //ServerIO
            buffer[s-1] = 0;
            // std::cout<<"client#"<<buffer<<std::endl;
            std::cout<<fdarray[index].clientip<<":"<<fdarray[index].clientport<<"#"<< buffer<<std::endl;
            //将接收到消息回传过去--这里也需要进行select进行管理，因为发送缓冲区可能存在满的情况
            std::string message = buffer;
            message += "[select server echo]";
            send(fdarray[index].fd,message.c_str(),message.size(),0);
        }else
        {
            if(s == 0)
            {
                logMessage(Info,"client quit...,fdarray[i]->defaultscok:%d->%d",fdarray[index].fd,defaultscok);
            }else
            {
                logMessage(Warning,",recv error,code:%d,string:%s,%d->%d",errno,strerror(errno),fdarray[index].fd,defaultscok);
            }
            close(fdarray[index].fd);
            fdarray[index].fd = defaultscok;  //清除
        }
    }

    // 根据不同的套接字进行不同的操作
    void HandlerEvent(fd_set &rfds,fd_set &wfds)
    {
        for (int i = 0; i < N; i++)
        {
           if(fdarray[i].fd == defaultscok) continue; 
         if((fdarray[i].event & RECV_EVENT) && FD_ISSET(fdarray[i].fd, &rfds)) // 非监听套接字  //读事件
         { 
               //处理读取 1.accept 2.rev
               if(fdarray[i].fd == listensock_.Fd()) 
               {
                Accepter();  //如果还是监听套接字需要进行accept
               }
               else if(fdarray[i].fd != listensock_.Fd()) 
               {
                Recv(i);
               }
               else
               {}
         } else if ((fdarray[i].event & WRITE_EVENT) && FD_ISSET(fdarray[i].fd, &wfds)) // 非监听套接字  //写事件
         {//ToDo

         }else
         {

         }
        }
    }
    void Start()
    {
        // 1.我们在启动阶段直接建立新的连接吗？ 如果在这里进行Accept会阻塞
        // 不能，
        // ①因为在最开始的时候，我们的服务器没有太多的sock，甚至只有一个！
        // ②而在网络中，只有新的连接到来才会被代表读事件就绪，所以我们不能直接进行获取新的连接！--》需要等待

        // demo0 版本0：使用select函数判断sock套接字是否已经就绪了
        // int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
        // demo1
        // fd_set rfds;                     // 用于监视读事件的文件描述符集合
        // FD_ZERO(&rfds);                  // 清空文件描述符集合
        // FD_SET(listensock_.Fd(), &rfds); // 设置要监视的文件描述符

        fdarray[0].fd = listensock_.Fd();
        fdarray[0].event = RECV_EVENT;
        while (true)
        {
            // ①因为rfds是一个输入输出型参数，注定了，每次都要对rfds进行重置，只要重置，必定要知道我历史上都有哪些fd？fdarray_[]。
            // ②因为服务器在运行中，sockfd的值一直在动态变化，所以maxfd也一定在变化，maxfd是不是也要进行动态更新，fdarray[]。
            fd_set rfds;
            fd_set wfds;
            FD_ZERO(&rfds);
            FD_ZERO(&wfds);
            int maxfd = fdarray[0].fd;
            for (int i = 0; i < N; i++)
            {
                if (fdarray[i].fd == defaultscok) continue;
                
                if(fdarray[i].event & RECV_EVENT)
                FD_SET(fdarray[i].fd, &rfds); // 设置用户要监视的sock集合
                if(fdarray[i].event & WRITE_EVENT)
                FD_SET(fdarray[i].fd, &wfds);
                if (maxfd < fdarray[i].fd)
                    maxfd = fdarray[i].fd; // 不断更新maxfd
            }
            // struct timeval timeout = {2, 0}; //{秒，微秒}
            int n = select(maxfd + 1, &rfds, &wfds, nullptr, nullptr);
            switch (n)
            {
            case 0:
                // 超时
                logMessage(Debug, "timeout %d %s", errno, strerror(errno));
                break;
            case -1:
                // 出错
                logMessage(Warning, "%d %s", errno, strerror(errno));
                break;
            default:
                // 成功
                logMessage(Debug, "有一个就绪事件发生了:%d", n);
                // 成功之后需要检查是否有可读事件发生
                HandlerEvent(rfds,wfds);
                DebugPrint();
                break;
            }
        }
    }
    // 将套接字打印出来看看
    void DebugPrint()
    {
        std::cout << "fdarray[]：";
        for (int i = 0; i < N; i++)
        {
            if (fdarray[i].fd != defaultscok)
            {
                std::cout << fdarray[i].fd << " ";
            }
        }
        std::cout << "\n";
    }
    ~SelectServer()
    {
        listensock_.Close();
    }

private:
    uint16_t port_;
    Sock listensock_;
    type_t fdarray[N];
};